```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>深入理解ThreadLocal原理与内存泄漏问题</title>
    <link href="https://cdn.staticfile.org/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <link href="https://cdn.staticfile.org/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid.min.js"></script>
    <style>
        body {
            font-family: 'Noto Sans SC', Tahoma, Arial, Roboto, "Droid Sans", "Helvetica Neue", "Droid Sans Fallback", "Heiti SC", "Hiragino Sans GB", Simsun, sans-serif;
            line-height: 1.8;
            color: #333;
        }
        h1, h2, h3, h4, h5, h6 {
            font-family: 'Noto Serif SC', serif;
            font-weight: 700;
            margin-top: 1.5em;
            margin-bottom: 0.5em;
            color: #111;
        }
        .hero {
            background: linear-gradient(135deg, #6e8efb 0%, #a777e3 100%);
            color: white;
            padding: 6rem 1rem;
            text-align: center;
            margin-bottom: 3rem;
            border-radius: 0 0 20px 20px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.1);
        }
        .hero h1 {
            font-size: 3.5rem;
            margin-bottom: 1.5rem;
            text-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        .hero p {
            font-size: 1.5rem;
            max-width: 800px;
            margin: 0 auto 2rem;
            opacity: 0.9;
        }
        .content {
            max-width: 900px;
            margin: 0 auto;
            padding: 0 1.5rem;
        }
        .code-block {
            background: #282c34;
            color: #abb2bf;
            padding: 1.5rem;
            border-radius: 8px;
            margin: 1.5rem 0;
            overflow-x: auto;
            font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
        }
        .code-block pre {
            margin: 0;
        }
        .highlight {
            background-color: rgba(255, 229, 100, 0.3);
            padding: 0.2em 0.4em;
            border-radius: 3px;
        }
        .card {
            background: white;
            border-radius: 8px;
            padding: 2rem;
            margin: 2rem 0;
            box-shadow: 0 4px 15px rgba(0,0,0,0.05);
            border-left: 4px solid #6e8efb;
        }
        .visualization {
            background: white;
            border-radius: 8px;
            padding: 2rem;
            margin: 2rem 0;
            box-shadow: 0 4px 15px rgba(0,0,0,0.05);
        }
        .footer {
            background: #1a202c;
            color: white;
            padding: 3rem 1rem;
            text-align: center;
            margin-top: 4rem;
        }
        .footer a {
            color: #a0aec0;
            text-decoration: none;
            transition: color 0.2s;
        }
        .footer a:hover {
            color: white;
            text-decoration: underline;
        }
        .tip {
            background: #f0f9ff;
            border-left: 4px solid #4299e1;
            padding: 1rem;
            margin: 1.5rem 0;
            border-radius: 0 4px 4px 0;
        }
        .warning {
            background: #fff5f5;
            border-left: 4px solid #f56565;
            padding: 1rem;
            margin: 1.5rem 0;
            border-radius: 0 4px 4px 0;
        }
        .first-letter {
            float: left;
            font-size: 4.5rem;
            line-height: 1;
            font-weight: bold;
            margin-right: 0.5rem;
            color: #6e8efb;
            margin-top: 0.3rem;
        }
        .floating-nav {
            position: fixed;
            right: 2rem;
            top: 50%;
            transform: translateY(-50%);
            background: white;
            border-radius: 8px;
            padding: 1rem;
            box-shadow: 0 4px 15px rgba(0,0,0,0.1);
            z-index: 100;
        }
        .floating-nav a {
            display: block;
            padding: 0.5rem 1rem;
            color: #4a5568;
            text-decoration: none;
            border-left: 3px solid transparent;
            transition: all 0.2s;
        }
        .floating-nav a:hover {
            color: #6e8efb;
            border-left: 3px solid #6e8efb;
        }
        @media (max-width: 1024px) {
            .floating-nav {
                display: none;
            }
        }
    </style>
</head>
<body>
    <div class="hero">
        <h1>深入理解ThreadLocal原理与内存泄漏问题</h1>
        <p>揭秘Java并发编程中的线程私有变量实现机制</p>
    </div>

    <div class="content">
        <div class="card">
            <div class="first-letter">你</div>
            <p>是否曾遇到这样的场景：一个对象需要在同一个线程内的多个方法中传递，却不想把它作为参数在每个方法间传来传去？或者你需要保存一个"线程上下文"信息，比如用户身份、事务ID或请求追踪信息？</p>
            
            <img src="https://picsum.photos/id/1/600/200" alt="并发编程示意图" class="w-full rounded-lg my-6">
            
            <p>在并发的世界里，全局变量是危险的。正如一个热闹的宿舍里，把你的私人物品放在公共区域，总会有人"不小心"把它们挪走。ThreadLocal就像是给每个线程发了一个专属保险柜，只有线程自己能存取，线程间互不干扰。</p>
            
            <p>然而，这个看似简单的工具类背后隐藏着巧妙的设计和潜在的风险。Java面试中，"ThreadLocal的原理和内存泄漏问题"几乎成了高频标配题。许多开发者对ThreadLocal的印象仅限于"每个线程拥有独立的变量副本"，但对其内存模型、实现机制和正确使用姿势知之甚少。</p>
        </div>

        <div id="section1" class="card">
            <h2>1. ThreadLocal的基础结构</h2>
            
            <h3>1.1 类继承关系</h3>
            <p>我们来看一下ThreadLocal的类定义：</p>
            
            <div class="code-block">
                <pre><code>public class ThreadLocal&lt;T&gt; {
    // 实现代码...
}</code></pre>
            </div>
            
            <p>ThreadLocal是一个泛型类，类型参数T表示它可以存储任何类型的对象。相比其他并发工具类，ThreadLocal的类结构非常简单，它没有继承任何类，也没有实现任何接口。</p>
            
            <p>这种设计反映了它的专注性——只做一件事：为每个线程提供独立的变量副本。</p>
            
            <h3>1.2 ThreadLocalMap内部类概述</h3>
            <p>ThreadLocal最核心的部分是其内部私有静态类ThreadLocalMap：</p>
            
            <div class="code-block">
                <pre><code>static class ThreadLocalMap {
    /**
     * Entry继承自WeakReference，key是ThreadLocal对象的弱引用
     */
    static class Entry extends WeakReference&lt;ThreadLocal&lt;?&gt;&gt; {
        /** 与这个ThreadLocal关联的值 */
        Object value;

        Entry(ThreadLocal&lt;?&gt; k, Object v) {
            super(k);
            value = v;
        }
    }

    /** 初始容量 - 必须是2的幂 */
    private static final int INITIAL_CAPACITY = 16;

    /** 存放数据的table，大小必须是2的幂 */
    private Entry[] table;

    /** table中的entry数量 */
    private int size = 0;

    /** 扩容阈值，默认为0 */
    private int threshold = 0;
    
    // 其他字段和方法...
}</code></pre>
            </div>
            
            <p>乍一看，这个ThreadLocalMap结构很像HashMap，都是基于哈希表实现的。但有几个关键区别：</p>
            <ol>
                <li>ThreadLocalMap是ThreadLocal的静态内部类，但它的实例是保存在Thread类中的</li>
                <li>Entry的key是ThreadLocal对象的弱引用，这对内存管理非常重要</li>
                <li>ThreadLocalMap没有实现Map接口，是一个定制的哈希表</li>
            </ol>
            
            <h3>1.3 弱引用键的设计理念</h3>
            <p>ThreadLocalMap中使用弱引用来保存ThreadLocal对象，这是一个精妙的设计：</p>
            
            <div class="code-block">
                <pre><code>static class Entry extends WeakReference&lt;ThreadLocal&lt;?&gt;&gt; {
    Object value;

    Entry(ThreadLocal&lt;?&gt; k, Object v) {
        super(k);  // 将ThreadLocal对象作为弱引用
        value = v;
    }
}</code></pre>
            </div>
            
            <p>为什么要用弱引用？想象这样一个场景：</p>
            
            <div class="code-block">
                <pre><code>void someMethod() {
    ThreadLocal&lt;User&gt; userThreadLocal = new ThreadLocal&lt;&gt;();
    userThreadLocal.set(new User("John"));
    
    // 使用userThreadLocal
    
    // 方法结束，userThreadLocal变量不再可访问
}</code></pre>
            </div>
            
            <p>当方法执行完毕，局部变量userThreadLocal就会被销毁，但是Thread对象中的ThreadLocalMap仍然持有对这个ThreadLocal的引用。如果是强引用，那么即使userThreadLocal变量已经不可访问，ThreadLocal对象也不会被垃圾回收，这可能导致内存泄漏。</p>
            
            <p>通过使用弱引用，一旦外部不再持有ThreadLocal的强引用，垃圾回收器就可以回收这个ThreadLocal对象，即使它仍被ThreadLocalMap引用。</p>
            
            <div class="warning">
                <i class="fas fa-exclamation-triangle mr-2"></i>
                <strong>注意：</strong>此时Entry中的value还是强引用，不会被自动回收，这就是常说的ThreadLocal内存泄漏的根源。后面我们会详细讨论这个问题。
            </div>
        </div>

        <div id="section2" class="card">
            <h2>2. ThreadLocalMap的实现机制</h2>
            
            <h3>2.1 Entry结构与弱引用</h3>
            <p>ThreadLocalMap内部使用Entry数组来存储数据：</p>
            
            <div class="code-block">
                <pre><code>private Entry[] table;

static class Entry extends WeakReference&lt;ThreadLocal&lt;?&gt;&gt; {
    Object value;
    
    Entry(ThreadLocal&lt;?&gt; k, Object v) {
        super(k);
        value = v;
    }
}</code></pre>
            </div>
            
            <p>每个Entry包含：</p>
            <ul>
                <li>一个ThreadLocal的弱引用作为key</li>
                <li>一个普通的强引用Object作为value</li>
            </ul>
            
            <p>值得注意的是，WeakReference是JDK提供的一种特殊引用类型，当一个对象只剩下弱引用指向它时，垃圾回收器会在下一次GC时回收该对象。</p>
            
            <div class="code-block">
                <pre><code>// 弱引用的基本使用
ThreadLocal&lt;?&gt; threadLocal = new ThreadLocal&lt;&gt;();
WeakReference&lt;ThreadLocal&lt;?&gt;&gt; weakRef = new WeakReference&lt;&gt;(threadLocal);

// 如果此时threadLocal = null，那么weakRef.get()在下一次GC后可能返回null
threadLocal = null;
System.gc();  // 触发GC
ThreadLocal&lt;?&gt; referent = weakRef.get();  // 可能已经是null</code></pre>
            </div>
            
            <p>这种设计让ThreadLocal实例在不再需要时可以被回收，同时Entry中的value依然存在，直到Thread结束或者手动调用remove()方法。</p>
            
            <h3>2.2 哈希表设计</h3>
            <p>ThreadLocalMap使用开放地址法解决哈希冲突：</p>
            
            <div class="code-block">
                <pre><code>private Entry getEntry(ThreadLocal&lt;?&gt; key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

private Entry getEntryAfterMiss(ThreadLocal&lt;?&gt; key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        ThreadLocal&lt;?&gt; k = e.get();
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

private static int nextIndex(int i, int len) {
    return ((i + 1 &lt; len) ? i + 1 : 0);
}</code></pre>
            </div>
            
            <p>与HashMap使用链表或红黑树解决冲突不同，ThreadLocalMap使用线性探测法：如果计算出的槽位已被占用，就继续往后查找，直到找到空槽位或找到目标Entry。</p>
            
            <p>ThreadLocalMap中每个ThreadLocal的哈希值通过一个特殊的原子递增生成器计算：</p>
            
            <div class="code-block">
                <pre><code>private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}</code></pre>
            </div>
            
            <p>这里使用的增量0x61c88647是一个神奇的数字，它是斐波那契散列乘数，能够使哈希值均匀分布，减少冲突。</p>
            
            <h3>2.3 解决哈希冲突的策略</h3>
            <p>当发生哈希冲突时，ThreadLocalMap采用线性探测法来解决：</p>
            
            <div class="code-block">
                <pre><code>private void set(ThreadLocal&lt;?&gt; key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal&lt;?&gt; k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}</code></pre>
            </div>
            
            <p>这段代码展示了ThreadLocalMap如何处理冲突：</p>
            <ol>
                <li>计算初始槽位i</li>
                <li>如果该位置已被占用，检查key是否相同
                    <ul>
                        <li>如果key相同，更新value并返回</li>
                        <li>如果key为null（已被GC回收），替换这个"陈旧"的Entry</li>
                        <li>否则，继续查找下一个位置</li>
                    </ul>
                </li>
                <li>如果找到空槽位，插入新Entry</li>
                <li>增加size并检查是否需要清理或扩容</li>
            </ol>
            
            <p>值得注意的是，在查找过程中如果发现key为null的Entry（表示ThreadLocal已被回收），会主动清理这些Entry，这是一种"顺手清理"的机制，有助于减少内存泄漏。</p>
        </div>

        <div id="section3" class="card">
            <h2>3. ThreadLocalMap的关键操作源码分析</h2>
            
            <h3>3.1 set方法实现</h3>
            <p>set方法是ThreadLocalMap最核心的方法之一，用于设置键值对：</p>
            
            <div class="code-block">
                <pre><code>private void set(ThreadLocal&lt;?&gt; key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal&lt;?&gt; k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}</code></pre>
            </div>
            
            <p>这段代码处理了三种情况：</p>
            <ol>
                <li>找到相同的key，更新value</li>
                <li>找到key为null的"陈旧"Entry，替换它</li>
                <li>找到空槽位，插入新Entry</li>
            </ol>
            
            <p>其中第2点很重要，这是ThreadLocalMap自动清理机制的一部分。当检测到key为null的Entry时，调用replaceStaleEntry方法替换并清理：</p>
            
            <div class="code-block">
                <pre><code>private void replaceStaleEntry(ThreadLocal&lt;?&gt; key, Object value, int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    Entry e;

    // 向前扫描，查找更多的陈旧Entry
    int slotToExpunge = staleSlot;
    for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) {
        if (e.get() == null)
            slotToExpunge = i;
    }

    // 向后遍历
    for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
        ThreadLocal&lt;?&gt; k = e.get();

        // 如果找到key，与staleSlot位置的Entry交换
        if (k == key) {
            e.value = value;

            tab[i] = tab[staleSlot];
            tab[staleSlot] = e;

            // 如果在前面的扫描中没有找到陈旧条目，则起点为这里
            if (slotToExpunge == staleSlot)
                slotToExpunge = i;
            
            // 清理陈旧的Entry
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
        }

        // 如果我们还没有找到staleSlot之前的陈旧条目
        if (k == null && slotToExpunge == staleSlot)
            slotToExpunge = i;
    }

    // 如果我们没有找到key，把新的Entry放入staleSlot
    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);

    // 如果有其他的陈旧Entry，清理它们
    if (slotToExpunge != staleSlot)
        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}</code></pre>
            </div>
            
            <p>这个方法相当复杂，它不仅替换了陈旧的Entry，还会主动清理其他陈旧Entry，以减少内存泄漏的可能性。</p>
            
            <h3>3.2 get方法实现</h3>
            <p>get方法用于获取与ThreadLocal关联的值：</p>
            
            <div class="code-block">
                <pre><code>private Entry getEntry(ThreadLocal&lt;?&gt; key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

private Entry getEntryAfterMiss(ThreadLocal&lt;?&gt; key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        ThreadLocal&lt;?&gt; k = e.get();
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}</code></pre>
            </div>
            
            <p>get方法首先计算哈希索引，然后检查该位置的Entry。如果key不匹配或为null，则调用getEntryAfterMiss继续查找。</p>
            
            <p>同样，在查找过程中如果发现key为null的Entry，会调用expungeStaleEntry进行清理，这是另一个"顺手清理"的时机。</p>
            
            <h3>3.3 remove方法实现</h3>
            <p>remove方法用于移除ThreadLocal关联的值：</p>
            
            <div class="code-block">
                <pre><code>private void remove(ThreadLocal&lt;?&gt; key) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            e.clear();  // 清除引用
            expungeStaleEntry(i);  // 清理这个槽位及后续陈旧的Entry
            return;
        }
    }
}</code></pre>
            </div>
            
            <p>remove方法首先查找key对应的Entry，找到后调用Entry.clear()方法清除ThreadLocal的弱引用，然后调用expungeStaleEntry清理这个槽位及后续可能的陈旧Entry。</p>
            
            <div class="tip">
                <i class="fas fa-lightbulb mr-2"></i>
                <strong>最佳实践：</strong>这个方法是彻底清除ThreadLocal数据的正确方式，它不仅移除了value的强引用，还处理了可能的连锁效应。
            </div>
            
            <h3>3.4 过期Entry的清理机制</h3>
            <p>ThreadLocalMap内部实现了多种清理机制来处理过期（陈旧）的Entry：</p>
            
            <div class="code-block">
                <pre><code>private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // 清除指定槽位的Entry
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // 重新哈希后续的Entry
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
        ThreadLocal&lt;?&gt; k = e.get();
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;
                
                // 如果需要，重新放置Entry到正确位置
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}

private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    Entry[] tab = table;
    int len = tab.length;
    do {
        i = nextIndex(i, len);
        Entry e = tab[i];
        if (e != null && e.get() == null) {
            n = len;
            removed = true;
            i = expungeStaleEntry(i);
        }
    } while ( (n >>>= 1) != 0);
    return removed;
}

private void rehash() {
    expungeStaleEntries();  // 完全清理所有陈旧Entry
    
    // 如果大小仍然超过阈值的3/4，则扩容
    if (size >= threshold - threshold / 4)
        resize();
}

private void expungeStaleEntries() {
    Entry[] tab = table;
    int len = tab.length;
    for (int j = 0; j < len; j++) {
        Entry e = tab[j];
        if (e != null && e.get() == null)
            expungeStaleEntry(j);
    }
}</code></pre>
            </div>
            
            <p>ThreadLocalMap提供了多个级别的清理机制：</p>
            <ol>
                <li><strong>定点清理</strong>：expungeStaleEntry方法清理指定位置的陈旧Entry，并重新哈希后续Entry</li>
                <li><strong>探测式清理</strong>：cleanSomeSlots方法在有限步数内查找并清理陈旧Entry</li>
                <li><strong>全量清理</strong>：expungeStaleEntries方法扫描整个表，清理所有陈旧Entry</li>
            </ol>
            
            <p>这些清理机制在不同操作中被触发：</p>
            <ul>
                <li>set操作可能触发探测式清理和rehash</li>
                <li>get操作在查找过程中可能触发定点清理</li>
                <li>remove操作总是触发定点清理</li>
                <li>当size达到阈值时，会触发全量清理和可能的扩容</li>
            </ul>
            
            <p>通过这些多层次的清理机制，ThreadLocalMap尽量减少内存泄漏的可能性，但并不能完全避免。</p>
        </div>

        <div id="section4" class="card">
            <h2>4. 核心操作源码分析</h2>
            
            <h3>4.1 ThreadLocal.set方法实现</h3>
            <p>ThreadLocal的set方法是我们直接使用的API，看看它是如何实现的：</p>
            
            <div class="code-block">
                <pre><code>public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}</code></pre>
            </div>
            
            <p>这个方法非常简洁：</p>
            <ol>
                <li>获取当前线程对象</li>
                <li>尝试获取线程中的ThreadLocalMap</li>
                <li>如果map存在，调用map.set设置值</li>
                <li>如果map不存在，创建一个新的ThreadLocalMap</li>
            </ol>
            
            <p>关键点在于：ThreadLocalMap实例是存储在Thread对象中的，而不是ThreadLocal对象中。每个Thread都有自己的ThreadLocalMap，用于存储所有与该线程关联的ThreadLocal变量。</p>
            
            <p>这就是ThreadLocal能够实现线程隔离的核心机制——数据实际上是存储在线程内部的。</p>
            
            <h3>4.2 ThreadLocal.get方法实现</h3>
            <p>get方法用于获取当前线程下的变量值：</p>
            
            <div class="code-block">
                <pre><code>public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

protected T initialValue() {
    return null;
}</code></pre>
            </div>
            
            <p>get方法的逻辑是：</p>
            <ol>
                <li>获取当前线程的ThreadLocalMap</li>
                <li>如果map存在，尝试获取this作为key的Entry</li>
                <li>如果Entry存在，返回其value</li>
                <li>否则，调用setInitialValue设置并返回初始值</li>
            </ol>
            
            <p>initialValue方法默认返回null，但可以被子类重写以提供自定义的初始值。这是一个很有用的特性，比如可以用来定义线程本地变量的默认值。</p>
            
            <h3>4.3 ThreadLocal.remove方法实现</h3>
            <p>remove方法用于移除当前线程中的变量：</p>
            
            <div class="code-block">
                <pre><code>public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}</code></pre>
            </div>
            
            <p>这个方法同样简单明了：获取当前线程的ThreadLocalMap，如果存在则从中移除this对应的Entry。</p>
            
            <div class="warning">
                <i class="fas fa-exclamation-triangle mr-2"></i>
                <strong>重要提示：</strong>正确调用remove方法对防止内存泄漏至关重要，尤其是在线程池环境中。线程被重用时，如果不清理ThreadLocal变量，新的任务可能会访问到之前任务设置的值，既造成了内存泄漏，又可能导致逻辑错误。
            </div>
            
            <h3>4.4 ThreadLocal.withInitial方法</h3>
            <p>从Java 8开始，ThreadLocal增加了一个工厂方法withInitial：</p>
            
            <div class="code-block">
                <pre><code>public static &lt;S&gt; ThreadLocal&lt;S&gt; withInitial(Supplier&lt;? extends S&gt; supplier) {
    return new SuppliedThreadLocal&lt;&gt;(supplier);
}

static final class SuppliedThreadLocal&lt;T&gt; extends ThreadLocal&lt;T&gt; {
    private final Supplier&lt;? extends T&gt; supplier;

    SuppliedThreadLocal(Supplier&lt;? extends T&gt; supplier) {
        this.supplier = Objects.requireNonNull(supplier);
    }

    @Override
    protected T initialValue() {
        return supplier.get();
    }
}</code></pre>
            </div>
            
            <p>这个方法创建一个特殊的ThreadLocal子类，它的initialValue方法使用提供的Supplier来获取初始值。这提供了一种更简洁的方式来创建带初始值的ThreadLocal：</p>
            
            <div class="code-block">
                <pre><code>// 旧方式
ThreadLocal&lt;SimpleDateFormat&gt; dateFormatThreadLocal = new ThreadLocal&lt;SimpleDateFormat&gt;() {
    @Override
    protected SimpleDateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
};

// 新方式
ThreadLocal&lt;SimpleDateFormat&gt; dateFormatThreadLocal = 
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));</code></pre>
            </div>
            
            <p>这种函数式风格更加简洁，也更符合Java 8的编程习惯。</p>
        </div>

        <div id="section5" class="card">
            <h2>5. 内存泄漏问题深度剖析</h2>
            
            <h3>5.1 为什么会发生内存泄漏</h3>
            <p>ThreadLocal的内存泄漏问题是众所周知的，让我们深入分析这个问题：</p>
            
            <div class="visualization">
                <div class="mermaid">
                    graph TD
                        Thread[Thread实例] --> ThreadLocalMap
                        ThreadLocalMap --> Entry1[Entry key:ThreadLocal1 value:Value1]
                        ThreadLocalMap --> Entry2[Entry key:ThreadLocal2 value:Value2]
                        ThreadLocalMap --> Entry3[Entry key:null value:Value3]
                </div>
            </div>
            
            <p>当ThreadLocal对象不再被引用时，Entry中的key变成null（因为是弱引用），但value仍然被强引用。如果线程长时间存活（如线程池中的线程），这些无法访问的value就会一直占用内存，形成泄漏。</p>
            
            <p>具体来说，泄漏发生的条件是：</p>
            <ol>
                <li>ThreadLocal对象不再被外部引用</li>
                <li>线程一直存活</li>
                <li>没有手动调用remove方法清理</li>
            </ol>
            
            <p>在这种情况下，虽然ThreadLocal对象会被回收，但其关联的value不会被回收，而且无法再通过正常手段访问到这些value。</p>
            
            <h3>5.2 弱引用与内存泄漏的关系</h3>
            <p>ThreadLocalMap中的Entry继承自WeakReference，key是ThreadLocal的弱引用：</p>
            
            <div class="code-block">
                <pre><code>static class Entry extends WeakReference&lt;ThreadLocal&lt;?&gt;&gt; {
    Object value;
    
    Entry(ThreadLocal&lt;?&gt; k, Object v) {
        super(k);
        value = v;
    }
}</code></pre>
            </div>
            
            <p>弱引用的特性是：当对象只被弱引用引用时，垃圾回收器会在下一次回收中回收该对象。这意味着当ThreadLocal对象不再被外部强引用时，Entry中的key会变成null。</p>
            
            <p>但这里有一个问题：虽然key会被回收，但value是强引用，不会自动回收。如果没有其他机制处理这些key为null的Entry，就会导致value无法被回收，形成内存泄漏。</p>
            
            <p>ThreadLocalMap的设计者意识到了这个问题，所以在get、set、remove等操作中添加了清理机制，但这些清理是"顺手"进行的，不保证及时清理所有过期Entry。</p>
            
            <h3>5.3 源码中的防泄漏设计</h3>
            <p>ThreadLocalMap中包含多种防止内存泄漏的设计，我们前面已经分析过：</p>
            <ol>
                <li><strong>在set过程中清理</strong>：发现key为null的Entry时，会调用replaceStaleEntry或expungeStaleEntry进行清理</li>
                <li><strong>在get过程中清理</strong>：getEntryAfterMiss方法中，发现key为null时会调用expungeStaleEntry清理</li>
                <li><strong>在remove时清理</strong>：remove方法直接调用expungeStaleEntry清理当前和后续的过期Entry</li>
                <li><strong>在扩容前全量清理</strong>：rehash方法首先调用expungeStaleEntries清理所有过期Entry</li>
            </ol>
            
            <p>这些清理机制都是为了减少内存泄漏的可能性，但它们都有一个共同的问题：必须有人调用ThreadLocalMap的方法才会触发清理。如果一个线程不再使用任何ThreadLocal，但线程本身还在运行，那么这些过期的Entry就不会被自动清理。</p>
            
            <h3>5.4 清理机制的触发时机</h3>
            <p>让我们总结一下ThreadLocalMap中清理机制的触发时机：</p>
            <ol>
                <li><strong>set时触发</strong>：向ThreadLocalMap中设置值时，可能触发清理</li>
                <li><strong>get时触发</strong>：获取值时，如果发生哈希冲突，可能触发清理</li>
                <li><strong>remove时触发</strong>：移除值时，必定触发清理</li>
                <li><strong>resize时触发</strong>：扩容前，会进行全量清理</li>
            </ol>
            
            <p>这些清理机制虽然可以减少内存泄漏的可能性，但都不能完全避免泄漏。最安全的做法仍然是在不再需要ThreadLocal变量时，显式调用remove方法进行清理。</p>
            
            <div class="warning">
                <i class="fas fa-exclamation-triangle mr-2"></i>
                <strong>特别提醒：</strong>特别是在线程池环境中，线程结束任务后会被重用，如果不清理ThreadLocal，可能导致数据污染和内存泄漏。因此，ThreadLocal变量的生命周期应该与任务的生命周期一致，而不是与线程的生命周期一致。
            </div>
        </div>
    </div>

    <div class="footer">
        <div class="container mx-auto">
            <p class="text-lg">技术小馆</p>
            <p class="mt-2">
                <a href="http://www.yuque.com/jtostring" target="_blank">http://www.yuque.com/jtostring</a>
            </p>
        </div>
    </div>

    <div class="floating-nav">
        <a href="#section1">1. 基础结构</a>
        <a href="#section2">2. 实现机制</a>
        <a href="#section3">3. 关键操作</a>
        <a href="#section4">4. 核心操作</a>
        <a href="#section5">5. 内存泄漏</a>
    </div>

    <script>
        mermaid.initialize({
            startOnLoad: true,
            theme: 'default',
            flowchart: {
                useMaxWidth: true,
                htmlLabels: true,
                curve: 'basis'
            }
        });
    </script>
</body>
</html>
```